﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// A class to keep track of different showtimes and which InternalSeats corresponds to the showtime.
/// A new entry is to be added to the dictionary every time a new showtime is made. (Or at least when it is accessed by a customer.)
/// </summary>
public class ReservedSeatList
{
    private static ReservedSeatList _instance;
    //private ReservedSeatList(){}

    public static ReservedSeatList Instance
    {
        get 
        { 
            if (_instance == null)
            {
                _instance = new ReservedSeatList();
            }
            return _instance;
        }
    }

    private Dictionary<ShowTime ,InternalSeats> seatsDict = new Dictionary<ShowTime ,InternalSeats>();
        
    public InternalSeats GetInternalSeats(ShowTime sht)
    {

        InternalSeats ret;
        //If date and room are the same, we can assume it's the right showtime.
        //try
        //{
        ret = seatsDict.Single(x => x.Key.CinemaRoom.Id == sht.CinemaRoom.Id && x.Key.DateTime == sht.DateTime).Value;
        //}
        //catch (InvalidOperationException)
        //{
        //    AddInternalSeats(sht, ret);
        //}
        //catch (Exception)
        //{
            
        //    throw;
        //}
        

        //Hoever, if the showtime does not exist, we can assume that we want to add it instead!
        return ret;
    }

    public void AddInternalSeats(ShowTime sht, InternalSeats internalSeats)
    {
        seatsDict.Add(sht, internalSeats);
    }

    public void AddInternalSeats(ShowTime sht)
    {
        seatsDict.Add(sht, new InternalSeats(sht));
    }

    public void RemoveInternalSeats(ShowTime sht)
    {
        //Only remove objects from the dictionary if they can no longer be used.
        if (sht.DateTime < DateTime.Now)
        {
            //Even though the references might not be exactly the same, we still want to remove the InternalSeats
            //Therefore it is assumed we can remove it, if the key shares the same DateTime and CinemaRoom, as these two properties makes a showtime unique.
            seatsDict.Remove(
                seatsDict.Single(x => x.Key.DateTime == sht.DateTime && x.Key.CinemaRoom == sht.CinemaRoom).Key);
        }
    }

        

}

/// <summary>
/// For every shiwtime there will be made a list here. This list keep track on which seats are already taken by other customers.
/// </summary>
public class InternalSeats
{    
    //This list is mase to make sure of which seats has been picked, to make sure it is possible for a user to select a seat.
    //Make sure the list is atomic!
    #region List of seats

    private static object _lock = new object();
        
    //This list (it's reference) should never be accessed by anyone else than the methids given in this class!
    private List<Seat> _seats;
    private CtrTicket ctt = new CtrTicket();
    private ShowTime sht;

    public InternalSeats(ShowTime showTime)
    {
        _seats = new List<Seat>();
        sht = showTime;
        PopulateSeats();
    }

    public bool SeatIsTaken(Seat seat)
    {
        bool taken = false;
        try
        {
            Monitor.Enter(_lock);
            foreach (var seat1 in _seats)
            {
                if (seat1.ToString().Equals(seat.ToString()))
                {
                    taken = true;
                    break;
                }
            }
        }
        finally
        {
            Monitor.Exit(_lock);
        }
        
        return taken;

    }
    public void AddSeat(Seat seat)
    {
        Monitor.Enter(_lock);
        try
        {
            if (!SeatIsTaken(seat))
            {
                _seats.Add(seat);
            }
        }
        finally
        {
            Monitor.Exit(_lock);
        }
    }

    public List<Seat> GetSeatList()
    {
        //We always want the list to be updated!
        PopulateSeats();
        //Make sure to return a new list instead of the reference to the original.
        List<Seat> retList = _seats.ToList();
        return retList;
    }

    public void RemoveSeat(Seat seat)
    {
        Monitor.Enter(_lock);
        try
        {
            _seats.Remove(seat);
        }
        finally
        {
            Monitor.Exit(_lock);
        }
    }

    public void PopulateSeats()
    {
        
        Monitor.Enter(_lock);
        try
        {
            _seats.Clear();
            List<Ticket> tickets = ctt.Read(sht);
            foreach (var ticket in tickets)
            {
                _seats.Add(ticket.Seat);
            }
        }
        finally
        {
            Monitor.Exit(_lock);
        }
    }

    #endregion
}
